Skip to content

State Exposure

ACTION REQUIRED

Legacynet is deprecating dry runs on October 10, 2025. Migrating to state exposure is now urgent for all processes running on Legacynet. This guide shows you how to expose your process state for direct HTTP access, providing dramatically better performance than dry runs.

HyperBEAM enables direct HTTP access to process state, eliminating the need for costly dryrun calls. This feature dramatically improves performance for web frontends and data services that need to read process data.

The Patch Device

The ~patch@1.0 device is the mechanism that allows AO processes to make parts of their internal state readable via direct HTTP GET requests.

How State Exposure Works

State exposure follows a simple four-step pattern:

  1. Process Logic: From your process (e.g., in Lua or WASM), send an outbound message to the ~patch@1.0 device.
  2. Patch Message Format: The message must include device and cache tags.
    lua
    Send({ Target = ao.id, device = 'patch@1.0', cache = { mydatakey = MyValue } })
  3. HyperBEAM Execution: HyperBEAM's dev_patch module processes this message, mapping the key-value pairs from the cache table to a URL path.
  4. HTTP Access: The exposed data is then immediately available via a standard HTTP GET request to the process's endpoint.
    HyperBEAM
    GET /<process-id>~process@1.0/compute/cache/<mydatakey>

Initial State Sync (Optional)

To make data available immediately on process creation, you can patch its initial state. A common pattern is to use a flag to ensure this sync only runs once, as shown in this example for a token's Balances and TotalSupply.

lua
-- Place this logic at the top level of your process script,
-- outside of specific handlers, so it runs on load.

Balances = { token1 = 100, token2 = 200 } -- A table of balances
TotalSupply = 1984 -- A single total supply value

-- 1. Initialize Flag:
-- Initializes a flag if it doesn't exist.
InitialSync = InitialSync or 'INCOMPLETE'

-- 2. Check Flag:
-- Checks if the sync has already run.
if InitialSync == 'INCOMPLETE' then
  -- 3. Patch State:
  -- The `Send` call patches the state, making it available at endpoints like:
  -- /cache/balances
  -- /cache/totalsupply
  Send({ device = 'patch@1.0', cache = { balances = Balances, totalsupply = TotalSupply } })
  -- 4. Update Flag:
  -- Updates the flag to prevent the sync from running again.
  InitialSync = 'COMPLETE'
  print("Initial state sync complete. Balances and TotalSupply patched.")
end

This pattern makes essential data queryable upon process creation, boosting application responsiveness.

Example (Lua in aos)

This handler exposes a currentstatus key that can be read via HTTP after the PublishData action is called.

lua
-- In your process code (e.g., loaded via .load)
Handlers.add(
  "PublishData",
  Handlers.utils.hasMatchingTag("Action", "PublishData"),
  function (msg)
    local dataToPublish = "Some important state: " .. math.random()
    -- Expose 'currentstatus' key under the 'cache' path
    Send({ device = 'patch@1.0', cache = { currentstatus = dataToPublish } })
    print("Published data to /cache/currentstatus")
  end
)

Avoiding Key Conflicts

Keys in the cache table become URL path segments. To avoid conflicts with reserved HyperBEAM paths, use descriptive, specific keys. Avoid using reserved keywords such as:

now, compute, state, info, test

For instance, prefer a key like myappstate over a generic key like state.

WARNING

HTTP paths are case-insensitive. While the patch device stores keys with case sensitivity (e.g., MyKey vs mykey), HTTP access to paths like the following is ambiguous and may lead to unpredictable results.

To prevent conflicts, always use lowercase keys in your cache table (e.g., mykey, usercount).

HyperBEAM
GET /<process-id>~process@1.0/cache/mykey

Key Points

  • Path Structure: Data is exposed at a path structured like this, where <key> is a key from your cache table:
    HyperBEAM
    /<process-id>~process@1.0/cache/<key>
  • Data Types: Basic data types like strings and numbers work best. Complex objects may require serialization.
  • compute vs now: Accessing patched data can be done via two main paths:
    HyperBEAM
    GET /<process-id>~process@1.0/compute/cache/...
    GET /<process-id>~process@1.0/now/cache/...
    The compute endpoint serves the last known value quickly, while now may perform additional computation to get the most recent state.
  • Read-Only Exposure: Patching is for efficient reads and does not replace your process's core state management logic.

Using the patch device enables efficient, standard HTTP access to your process state, seamlessly connecting decentralized logic with web applications.

Patching User-Owned Processes

If your application spawns processes that are owned by users (not your application), you'll need to provide a way for users to patch their own processes. A common example is marketplace applications where each user has their own process instance.

Implementation Strategy

You can create UI components that allow users to update their processes. Here's an example approach:

javascript
import { message, createDataItemSigner } from "@permaweb/aoconnect";

async function patchUserProcess(processId) {
  // User must sign this message themselves
  const signer = createDataItemSigner(window.arweaveWallet);

  const messageId = await message({
    process: processId,
    signer,
    tags: [{ name: "Action", value: "UpdateToPatch" }],
  });

  return messageId;
}

In your process code, add a handler that users can trigger:

lua
Handlers.add(
  "UpdateToPatch",
  Handlers.utils.hasMatchingTag("Action", "UpdateToPatch"),
  function(msg)
    -- Only allow the process owner to update
    if msg.From ~= ao.id then
      print("Only the process owner can update to use patch")
      return
    end

    -- Add your patch logic here
    Send({
      device = 'patch@1.0',
      cache = {
        -- Add the state you want to expose
      }
    })

    print("Process updated to use state patching")
  end
)

This allows users to maintain ownership of their processes while still benefiting from HyperBEAM's performance improvements.

For a complete guide on implementing this pattern, see User-Owned Processes.

Next Steps

With state exposure configured, you can now add dynamic reads to compute values on-the-fly without modifying your process state.